/* packet-lbttcp.c
 * Routines for LBM TCP Packet dissection
 *
 * Copyright (c) 2005-2014 Informatica Corporation. All Rights Reserved.
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "config.h"

#include <epan/packet.h>
#include <epan/prefs.h>
#include <epan/uat.h>
#include <epan/to_str.h>
#include "packet-tcp.h"
#include "packet-lbm.h"
#include "packet-lbttcp.h"

void proto_register_lbttcp(void);
void proto_reg_handoff_lbttcp(void);

/* Protocol handle */
static int proto_lbttcp;

/* Dissector handle */
static dissector_handle_t lbttcp_dissector_handle;

/*----------------------------------------------------------------------------*/
/* LBT-TCP protocol management.                                               */
/*----------------------------------------------------------------------------*/

typedef struct
{
    wmem_tree_t * frame_tree;
    wmem_tree_t * session_tree;
} lbttcp_transport_conv_data_t;

static const address lbttcp_null_address = ADDRESS_INIT_NONE;

lbttcp_transport_t * lbttcp_transport_find(const address * source_address, uint16_t source_port, uint32_t session_id, uint32_t frame)
{
    lbttcp_transport_t * entry = NULL;
    conversation_t * conv = NULL;
    lbttcp_transport_conv_data_t * conv_data = NULL;

    conv = find_conversation(frame, source_address, &lbttcp_null_address, CONVERSATION_TCP, source_port, 0, 0);
    if (conv != NULL)
    {
        conv_data = (lbttcp_transport_conv_data_t *) conversation_get_proto_data(conv, proto_lbttcp);
        if (conv_data != NULL)
        {
            entry = (lbttcp_transport_t *) wmem_tree_lookup32(conv_data->session_tree, session_id);
        }
    }
    return (entry);
}

static lbttcp_transport_t * lbttcp_transport_create(const address * source_address, uint16_t source_port, uint32_t session_id)
{
    lbttcp_transport_t * transport = NULL;

    transport = wmem_new(wmem_file_scope(), lbttcp_transport_t);
    copy_address_wmem(wmem_file_scope(), &(transport->source_address), source_address);
    transport->source_port = source_port;
    transport->session_id = session_id;
    transport->channel = lbm_channel_assign(LBM_CHANNEL_TRANSPORT_LBTTCP);
    transport->next_client_id = 1;
    transport->client_list = wmem_list_new(wmem_file_scope());
    return (transport);
}

lbttcp_transport_t * lbttcp_transport_add(const address * source_address, uint16_t source_port, uint32_t session_id, uint32_t frame)
{
    lbttcp_transport_t * entry = NULL;
    conversation_t * conv = NULL;
    lbttcp_transport_conv_data_t * conv_data = NULL;

    conv = find_conversation(frame, source_address, &lbttcp_null_address, CONVERSATION_TCP, source_port, 0, 0);
    if (conv == NULL)
    {
        conv = conversation_new(frame, source_address, &lbttcp_null_address, CONVERSATION_TCP, source_port, 0, 0);
    }
    conv_data = (lbttcp_transport_conv_data_t *) conversation_get_proto_data(conv, proto_lbttcp);
    if (conv_data == NULL)
    {
        conv_data = wmem_new(wmem_file_scope(), lbttcp_transport_conv_data_t);
        conv_data->frame_tree = wmem_tree_new(wmem_file_scope());
        conv_data->session_tree = wmem_tree_new(wmem_file_scope());
        conversation_add_proto_data(conv, proto_lbttcp, (void *) conv_data);
    }
    entry = (lbttcp_transport_t *) wmem_tree_lookup32(conv_data->session_tree, session_id);
    if (entry != NULL)
    {
        return (entry);
    }
    entry = lbttcp_transport_create(source_address, source_port, session_id);
    wmem_tree_insert32(conv_data->session_tree, session_id, (void *) entry);
    wmem_tree_insert32(conv_data->frame_tree, frame, (void *) entry);
    return (entry);
}

static lbttcp_client_transport_t * lbttcp_client_transport_find(lbttcp_transport_t * transport, const address * receiver_address, uint16_t receiver_port, uint32_t frame)
{
    lbttcp_client_transport_t * entry = NULL;
    conversation_t * client_conv = NULL;

    if (transport == NULL)
    {
        return (NULL);
    }
    client_conv = find_conversation(frame, &(transport->source_address), receiver_address, CONVERSATION_TCP, transport->source_port, receiver_port, 0);
    if (client_conv != NULL)
    {
        wmem_tree_t * session_tree = NULL;

        session_tree = (wmem_tree_t *) conversation_get_proto_data(client_conv, proto_lbttcp);
        if (session_tree != NULL)
        {
            entry = (lbttcp_client_transport_t *) wmem_tree_lookup32(session_tree, transport->session_id);
        }
    }
    return (entry);
}

static lbttcp_client_transport_t * lbttcp_client_transport_add(lbttcp_transport_t * transport, const address * receiver_address, uint16_t receiver_port, uint32_t frame)
{
    lbttcp_client_transport_t * entry;
    conversation_t * client_conv = NULL;
    wmem_tree_t * session_tree = NULL;

    if (transport == NULL)
    {
        return (NULL);
    }
    entry = lbttcp_client_transport_find(transport, receiver_address, receiver_port, frame);
    if (entry != NULL)
    {
        return (entry);
    }
    entry = wmem_new(wmem_file_scope(), lbttcp_client_transport_t);
    copy_address_wmem(wmem_file_scope(), &(entry->receiver_address), receiver_address);
    entry->receiver_port = receiver_port;
    entry->id = transport->next_client_id++;

    /* See if a conversation for this address/port pair exists. */
    client_conv = find_conversation(frame, &(transport->source_address), receiver_address, CONVERSATION_TCP, transport->source_port, receiver_port, 0);
    if (client_conv == NULL)
    {
        client_conv = conversation_new(frame, &(transport->source_address), receiver_address, CONVERSATION_TCP, transport->source_port, receiver_port, 0);
        session_tree = wmem_tree_new(wmem_file_scope());
        conversation_add_proto_data(client_conv, proto_lbttcp, (void *) session_tree);
    }
    session_tree = (wmem_tree_t *) conversation_get_proto_data(client_conv, proto_lbttcp);
    if (session_tree == NULL)
    {
        session_tree = wmem_tree_new(wmem_file_scope());
        conversation_add_proto_data(client_conv, proto_lbttcp, (void *) session_tree);
    }
    wmem_tree_insert32(session_tree, transport->session_id, (void *) entry);

    /* Add this client to the transport. */
    wmem_list_append(transport->client_list, (void *) entry);
    return (entry);
}

char * lbttcp_transport_source_string(const address * source_address, uint16_t source_port, uint32_t session_id)
{
    char * bufptr = NULL;

    if (session_id == 0)
    {
        bufptr = wmem_strdup_printf(wmem_file_scope(), "TCP:%s:%" PRIu16, address_to_str(wmem_packet_scope(), source_address), source_port);
    }
    else
    {
        bufptr = wmem_strdup_printf(wmem_file_scope(), "TCP:%s:%" PRIu16 ":%08x", address_to_str(wmem_packet_scope(), source_address), source_port, session_id);
    }
    return (bufptr);
}

bool lbttcp_transport_sid_find(const address * source_address, uint16_t source_port, uint32_t frame, uint32_t * session_id)
{
    conversation_t * conv = NULL;
    lbttcp_transport_conv_data_t * conv_data = NULL;
    lbttcp_transport_t * transport = NULL;

    conv = find_conversation(frame, source_address, &lbttcp_null_address, CONVERSATION_TCP, source_port, 0, 0);
    if (conv == NULL)
    {
        return false;
    }
    conv_data = (lbttcp_transport_conv_data_t *) conversation_get_proto_data(conv, proto_lbttcp);
    if (conv_data == NULL)
    {
        return false;
    }
    if (conv_data->frame_tree == NULL)
    {
        return false;
    }
    transport = (lbttcp_transport_t *)wmem_tree_lookup32_le(conv_data->frame_tree, frame);
    if (transport == NULL)
    {
        return false;
    }
    *session_id = transport->session_id;
    return true;
}

void lbttcp_transport_sid_add(const address * source_address, uint16_t source_port, uint32_t frame, uint32_t session_id)
{
    conversation_t * conv = NULL;
    lbttcp_transport_conv_data_t * conv_data = NULL;
    lbttcp_transport_t * transport = NULL;

    conv = find_conversation(frame, source_address, &lbttcp_null_address, CONVERSATION_TCP, source_port, 0, 0);
    if (conv == NULL)
    {
        conv = conversation_new(frame, source_address, &lbttcp_null_address, CONVERSATION_TCP, source_port, 0, 0);
    }
    conv_data = (lbttcp_transport_conv_data_t *) conversation_get_proto_data(conv, proto_lbttcp);
    if (conv_data == NULL)
    {
        conv_data = wmem_new(wmem_file_scope(), lbttcp_transport_conv_data_t);
        conv_data->frame_tree = wmem_tree_new(wmem_file_scope());
        conv_data->session_tree = wmem_tree_new(wmem_file_scope());
        conversation_add_proto_data(conv, proto_lbttcp, (void *) conv_data);
    }
    /* Lookup by frame */
    transport = (lbttcp_transport_t *) wmem_tree_lookup32_le(conv_data->frame_tree, frame);
    if (transport != NULL)
    {
        if (transport->session_id != session_id)
        {
            transport = NULL;
        }
    }
    if (transport == NULL)
    {
        transport = lbttcp_transport_create(source_address, source_port, session_id);
        wmem_tree_insert32(conv_data->session_tree, session_id, (void *) transport);
        wmem_tree_insert32(conv_data->frame_tree, frame, (void *) transport);
    }
}

/*----------------------------------------------------------------------------*/
/* Preferences.                                                               */
/*----------------------------------------------------------------------------*/

/* Preferences default values. */
#define LBTTCP_DEFAULT_SOURCE_PORT_LOW 14371
#define LBTTCP_DEFAULT_SOURCE_PORT_HIGH 14390
#define LBTTCP_DEFAULT_REQUEST_PORT_LOW 14391
#define LBTTCP_DEFAULT_REQUEST_PORT_HIGH 14395
#define LBTTCP_DEFAULT_STORE_PORT_LOW 0
#define LBTTCP_DEFAULT_STORE_PORT_HIGH 0

/* Global preferences variables (altered by the preferences dialog). */
static uint32_t global_lbttcp_source_port_low = LBTTCP_DEFAULT_SOURCE_PORT_LOW;
static uint32_t global_lbttcp_source_port_high  = LBTTCP_DEFAULT_SOURCE_PORT_HIGH;
static uint32_t global_lbttcp_request_port_low = LBTTCP_DEFAULT_REQUEST_PORT_LOW;
static uint32_t global_lbttcp_request_port_high = LBTTCP_DEFAULT_REQUEST_PORT_HIGH;
static uint32_t global_lbttcp_store_port_low = LBTTCP_DEFAULT_STORE_PORT_LOW;
static uint32_t global_lbttcp_store_port_high = LBTTCP_DEFAULT_STORE_PORT_HIGH;
static bool global_lbttcp_use_tag;

/* Local preferences variables (used by the dissector). */
static uint32_t lbttcp_source_port_low = LBTTCP_DEFAULT_SOURCE_PORT_LOW;
static uint32_t lbttcp_source_port_high = LBTTCP_DEFAULT_SOURCE_PORT_HIGH;
static uint32_t lbttcp_request_port_low = LBTTCP_DEFAULT_REQUEST_PORT_LOW;
static uint32_t lbttcp_request_port_high = LBTTCP_DEFAULT_REQUEST_PORT_HIGH;
static uint32_t lbttcp_store_port_low = LBTTCP_DEFAULT_STORE_PORT_LOW;
static uint32_t lbttcp_store_port_high = LBTTCP_DEFAULT_STORE_PORT_HIGH;
static bool lbttcp_use_tag;

/* Tag definitions. */
typedef struct
{
    char * name;
    uint32_t source_port_low;
    uint32_t source_port_high;
    uint32_t request_port_low;
    uint32_t request_port_high;
    uint32_t store_port_low;
    uint32_t store_port_high;
} lbttcp_tag_entry_t;

static lbttcp_tag_entry_t * lbttcp_tag_entry;
static unsigned lbttcp_tag_count;

UAT_CSTRING_CB_DEF(lbttcp_tag, name, lbttcp_tag_entry_t)
UAT_DEC_CB_DEF(lbttcp_tag, source_port_low, lbttcp_tag_entry_t)
UAT_DEC_CB_DEF(lbttcp_tag, source_port_high, lbttcp_tag_entry_t)
UAT_DEC_CB_DEF(lbttcp_tag, request_port_low, lbttcp_tag_entry_t)
UAT_DEC_CB_DEF(lbttcp_tag, request_port_high, lbttcp_tag_entry_t)
UAT_DEC_CB_DEF(lbttcp_tag, store_port_low, lbttcp_tag_entry_t)
UAT_DEC_CB_DEF(lbttcp_tag, store_port_high, lbttcp_tag_entry_t)
static uat_field_t lbttcp_tag_array[] =
{
    UAT_FLD_CSTRING(lbttcp_tag, name, "Tag name", "Tag name"),
    UAT_FLD_DEC(lbttcp_tag, source_port_low, "Source port low", "Source port low"),
    UAT_FLD_DEC(lbttcp_tag, source_port_high, "Source port high", "Source port high"),
    UAT_FLD_DEC(lbttcp_tag, request_port_low, "Request port low", "Request port low"),
    UAT_FLD_DEC(lbttcp_tag, request_port_high, "Request port high", "Request port high"),
    UAT_FLD_DEC(lbttcp_tag, store_port_low, "Store port low", "Store port low"),
    UAT_FLD_DEC(lbttcp_tag, store_port_high, "Store port high", "Store port high"),
    UAT_END_FIELDS
};

/*----------------------------------------------------------------------------*/
/* UAT callback functions.                                                    */
/*----------------------------------------------------------------------------*/
static bool lbttcp_tag_update_cb(void * record, char * * error_string)
{
    lbttcp_tag_entry_t * tag = (lbttcp_tag_entry_t *)record;

    if (tag->name == NULL)
    {
        *error_string = g_strdup("Tag name can't be empty");
        return false;
    }
    else
    {
        g_strstrip(tag->name);
        if (tag->name[0] == 0)
        {
            *error_string = g_strdup("Tag name can't be empty");
            return false;
        }
    }
    return true;
}

static void * lbttcp_tag_copy_cb(void * destination, const void * source, size_t length _U_)
{
    const lbttcp_tag_entry_t * src = (const lbttcp_tag_entry_t *)source;
    lbttcp_tag_entry_t * dest = (lbttcp_tag_entry_t *)destination;

    dest->name = g_strdup(src->name);
    dest->source_port_low = src->source_port_low;
    dest->source_port_high = src->source_port_high;
    dest->request_port_low = src->request_port_low;
    dest->request_port_high = src->request_port_high;
    dest->store_port_low = src->store_port_low;
    dest->store_port_high = src->store_port_high;
    return (dest);
}

static void lbttcp_tag_free_cb(void * record)
{
    lbttcp_tag_entry_t * tag = (lbttcp_tag_entry_t *)record;

    if (tag->name != NULL)
    {
        g_free(tag->name);
        tag->name = NULL;
    }
}

static const lbttcp_tag_entry_t * lbttcp_tag_locate(packet_info * pinfo)
{
    unsigned idx;
    const lbttcp_tag_entry_t * tag = NULL;

    if (!lbttcp_use_tag)
    {
        return (NULL);
    }

    for (idx = 0; idx < lbttcp_tag_count; ++idx)
    {
        tag = &(lbttcp_tag_entry[idx]);
        if (((pinfo->srcport >= tag->source_port_low) && (pinfo->srcport <= tag->source_port_high))
            || ((pinfo->destport >= tag->source_port_low) && (pinfo->destport <= tag->source_port_high))
            || ((pinfo->srcport >= tag->request_port_low) && (pinfo->srcport <= tag->request_port_high))
            || ((pinfo->destport >= tag->request_port_low) && (pinfo->destport <= tag->request_port_high))
            || ((pinfo->srcport >= tag->store_port_low) && (pinfo->srcport <= tag->store_port_high))
            || ((pinfo->destport >= tag->store_port_low) && (pinfo->destport <= tag->store_port_high)))
        {
            return (tag);
        }
    }
    return (NULL);
}

static char * lbttcp_tag_find(packet_info * pinfo)
{
    const lbttcp_tag_entry_t * tag = NULL;

    if (!lbttcp_use_tag)
    {
        return (NULL);
    }

    tag = lbttcp_tag_locate(pinfo);
    if (tag != NULL)
    {
        return tag->name;
    }
    return (NULL);
}

/*----------------------------------------------------------------------------*/
/* Handles of all types.                                                      */
/*----------------------------------------------------------------------------*/

/* Dissector tree handles */
static int ett_lbttcp;
static int ett_lbttcp_channel;

/* Dissector field handles */
static int hf_lbttcp_tag;
static int hf_lbttcp_channel;
static int hf_lbttcp_channel_id;
static int hf_lbttcp_channel_client;

static bool lbttcp_packet_is_transport_source(packet_info * pinfo, const lbttcp_tag_entry_t * tag)
{
    bool is_transport_source_packet = false;

    if (tag == NULL)
    {
        if ((pinfo->srcport >= lbttcp_source_port_low) && (pinfo->srcport <= lbttcp_source_port_high))
        {
            is_transport_source_packet = true;
        }
    }
    else
    {
        if ((pinfo->srcport >= tag->source_port_low) && (pinfo->srcport <= tag->source_port_high))
        {
            is_transport_source_packet = true;
        }
    }
    return (is_transport_source_packet);
}

static bool lbttcp_packet_is_transport_client(packet_info * pinfo, const lbttcp_tag_entry_t * tag)
{
    bool is_transport_client_packet = false;

    if (tag == NULL)
    {
        if ((pinfo->destport >= lbttcp_source_port_low) && (pinfo->destport <= lbttcp_source_port_high))
        {
            is_transport_client_packet = true;
        }
    }
    else
    {
        if ((pinfo->destport >= tag->source_port_low) && (pinfo->destport <= tag->source_port_high))
        {
            is_transport_client_packet = true;
        }
    }
    return (is_transport_client_packet);
}

static unsigned get_lbttcp_pdu_length(packet_info * pinfo _U_, tvbuff_t * tvb,
                                   int offset, void *data _U_)
{
    return lbmc_get_message_length(tvb, offset);
}

static int dissect_lbttcp_pdu(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, void * dissector_data _U_)
{
    proto_tree * lbttcp_tree = NULL;
    proto_item * ti = NULL;
    char * tag_name = NULL;
    int len_dissected;
    const lbttcp_tag_entry_t * tag = NULL;
    uint64_t channel = LBM_CHANNEL_NO_CHANNEL;
    uint32_t client_id = 0;
    bool from_source = false;
    bool transport_packet = false;

    if (lbttcp_use_tag)
    {
        tag = lbttcp_tag_locate(pinfo);
        tag_name = lbttcp_tag_find(pinfo);
    }
    if (tag_name != NULL)
    {
        ti = proto_tree_add_protocol_format(tree, proto_lbttcp, tvb, 0, -1, "LBT-TCP Protocol (Tag: %s)", tag_name);
    }
    else
    {
        ti = proto_tree_add_protocol_format(tree, proto_lbttcp, tvb, 0, -1, "LBT-TCP Protocol");
    }
    lbttcp_tree = proto_item_add_subtree(ti, ett_lbttcp);
    if (tag_name != NULL)
    {
        proto_item * item = NULL;

        item = proto_tree_add_string(lbttcp_tree, hf_lbttcp_tag, tvb, 0, 0, tag_name);
        proto_item_set_generated(item);
    }
    if (lbttcp_packet_is_transport_source(pinfo, tag))
    {
        from_source = true;
        transport_packet = true;
    }
    else if (lbttcp_packet_is_transport_client(pinfo, tag))
    {
        from_source = false;
        transport_packet = true;
    }
    if (transport_packet)
    {
        address source_address;
        address client_address;
        uint16_t srcport;
        uint16_t clntport;
        uint32_t sid = 0;
        lbttcp_transport_t * transport = NULL;
        lbttcp_client_transport_t * client = NULL;

        if (from_source)
        {
            copy_address_shallow(&source_address, &(pinfo->src));
            srcport = pinfo->srcport;
            copy_address_shallow(&client_address, &(pinfo->dst));
            clntport = pinfo->destport;
        }
        else
        {
            copy_address_shallow(&source_address, &(pinfo->dst));
            srcport = pinfo->destport;
            copy_address_shallow(&client_address, &(pinfo->src));
            clntport = pinfo->srcport;
        }
        /* See if we have a matching transport with no session ID. */
        transport = lbttcp_transport_find(&source_address, srcport, sid, pinfo->num);
        if (transport == NULL)
        {
            /* See if we know about a SID */
            if (lbttcp_transport_sid_find(&source_address, srcport, pinfo->num, &sid))
            {
                transport = lbttcp_transport_find(&source_address, srcport, sid, pinfo->num);
            }
        }
        if (transport != NULL)
        {
            channel = transport->channel;
            /* See if we already know about this client */
            client = lbttcp_client_transport_find(transport, &client_address, clntport, pinfo->num);
            if (client == NULL)
            {
                /* No - add it. */
                client = lbttcp_client_transport_add(transport, &client_address, clntport, pinfo->num);
            }
            if (client != NULL)
            {
                client_id = client->id;
            }
        }
        else
        {
            if (PINFO_FD_VISITED(pinfo))
            {
                /* No TIR and no session ID seen, so create the transport */
                transport = lbttcp_transport_add(&source_address, srcport, 0, pinfo->num);
                if (transport != NULL)
                {
                    channel = transport->channel;
                    client = lbttcp_client_transport_add(transport, &client_address, clntport, pinfo->num);
                    if (client != NULL)
                    {
                        client_id = client->id;
                    }
                }
            }
            else
            {
                /* Defer determining the channel. */
                if (from_source)
                {
                    channel = lbm_channel_assign_unknown_transport_source_lbttcp();
                }
                else
                {
                    channel = lbm_channel_assign_unknown_transport_client_lbttcp();
                }
            }
        }
    }
    else
    {
        channel = lbm_channel_assign_unknown_stream_tcp();
    }
    if (lbm_channel_is_known(channel))
    {
        proto_item * channel_item = NULL;
        proto_tree * channel_tree = NULL;

        channel_item = proto_tree_add_item(lbttcp_tree, hf_lbttcp_channel, tvb, 0, 0, ENC_NA);
        proto_item_set_generated(channel_item);
        channel_tree = proto_item_add_subtree(channel_item, ett_lbttcp_channel);
        channel_item = proto_tree_add_uint64(channel_tree, hf_lbttcp_channel_id, tvb, 0, 0, channel);
        proto_item_set_generated(channel_item);
        channel_item = proto_tree_add_uint(channel_tree, hf_lbttcp_channel_client, tvb, 0, 0, client_id);
        proto_item_set_generated(channel_item);
    }
    len_dissected = lbmc_dissect_lbmc_packet(tvb, 0, pinfo, tree, tag_name, channel);
    return (len_dissected);
}

/*
 * dissect_lbttcp_real - The "common" dissection for LBT-TCP
 */
static int dissect_lbttcp_real(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, void * data _U_)
{
    char * tag_name = NULL;

    col_set_str(pinfo->cinfo, COL_PROTOCOL, "LBT-TCP");
    col_clear(pinfo->cinfo, COL_INFO);
    if (lbttcp_use_tag)
    {
        tag_name = lbttcp_tag_find(pinfo);
    }
    if (tag_name != NULL)
    {
        col_add_fstr(pinfo->cinfo, COL_INFO, "[Tag: %s]", tag_name);
    }
    col_set_fence(pinfo->cinfo, COL_INFO);
    tcp_dissect_pdus(tvb, pinfo, tree, true, lbmc_get_minimum_length(), /* Need at least the msglen */
        get_lbttcp_pdu_length, dissect_lbttcp_pdu, NULL);

    return tvb_captured_length(tvb);
}

/*
 * dissect_lbttcp - The dissector for LBT-TCP
 */
static int dissect_lbttcp(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, void * data)
{
    if (!lbmc_test_lbmc_header(tvb, 0))
        return 0;

    return dissect_lbttcp_real(tvb, pinfo, tree, data);
}

static bool test_lbttcp_packet(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, void * data)
{
    /* Destination address must be IPV4 and 4 bytes in length. */
    if ((pinfo->dst.type != AT_IPv4) || (pinfo->dst.len != 4))
    {
        return false;
    }

    if (lbttcp_use_tag)
    {
        if (lbttcp_tag_find(pinfo) != NULL)
        {
            dissect_lbttcp_real(tvb, pinfo, tree, data);
            return true;
        }
        else
        {
            return false;
        }
    }

    /*
        Source port or destination port must be in the source port range, or destination port must be in
        the request port range, or either port in the UME store port range.
    */
    if (!(((pinfo->srcport >= lbttcp_source_port_low) && (pinfo->srcport <= lbttcp_source_port_high))
          || ((pinfo->destport >= lbttcp_source_port_low) && (pinfo->destport <= lbttcp_source_port_high))
          || ((pinfo->srcport >= lbttcp_request_port_low) && (pinfo->srcport <= lbttcp_request_port_high))
          || ((pinfo->destport >= lbttcp_request_port_low) && (pinfo->destport <= lbttcp_request_port_high))
          || ((pinfo->srcport >= lbttcp_store_port_low) && (pinfo->srcport <= lbttcp_store_port_high))
          || ((pinfo->destport >= lbttcp_store_port_low) && (pinfo->destport <= lbttcp_store_port_high))))
    {
        return false;
    }

    if (!lbmc_test_lbmc_header(tvb, 0))
        return false;

    /* One of ours. Probably. */
    dissect_lbttcp_real(tvb, pinfo, tree, data);
    return true;
}

/* Register all the bits needed with the filtering engine */
void proto_register_lbttcp(void)
{
    static hf_register_info hf[] =
    {
        { &hf_lbttcp_tag,
            { "Tag", "lbttcp.tag", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } },
        { &hf_lbttcp_channel,
            { "Channel", "lbttcp.channel", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } },
        { &hf_lbttcp_channel_id,
            { "Channel ID", "lbttcp.channel.channel", FT_UINT64, BASE_HEX_DEC, NULL, 0x0, NULL, HFILL } },
        { &hf_lbttcp_channel_client,
            { "Channel Client", "lbttcp.channel.client", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } },
    };
    static int * ett[] =
    {
        &ett_lbttcp,
        &ett_lbttcp_channel
    };
    module_t * lbttcp_module;
    uat_t * tag_uat;

    proto_lbttcp = proto_register_protocol("LBT TCP Protocol", "LBT-TCP", "lbttcp");

    proto_register_field_array(proto_lbttcp, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));

    lbttcp_dissector_handle = register_dissector("lbttcp", dissect_lbttcp, proto_lbttcp);

    lbttcp_module = prefs_register_protocol_subtree("29West", proto_lbttcp, proto_reg_handoff_lbttcp);
    prefs_register_uint_preference(lbttcp_module,
        "source_port_low",
        "Source port range low (default " MAKESTRING(LBTTCP_DEFAULT_SOURCE_PORT_LOW)")",
        "Set the low end of the LBT-TCP source TCP port range (context transport_tcp_port_low)",
        10,
        &global_lbttcp_source_port_low);

    prefs_register_uint_preference(lbttcp_module,
        "source_port_high",
        "Source port range high (default " MAKESTRING(LBTTCP_DEFAULT_SOURCE_PORT_HIGH)")",
        "Set the high end of the LBT-TCP source TCP port range (context transport_tcp_port_high)",
        10,
        &global_lbttcp_source_port_high);

    prefs_register_uint_preference(lbttcp_module,
        "request_port_low",
        "Request port range low (default " MAKESTRING(LBTTCP_DEFAULT_REQUEST_PORT_LOW)")",
        "Set the low end of the LBT-TCP request TCP port range (context request_tcp_port_low)",
        10,
        &global_lbttcp_request_port_low);

    prefs_register_uint_preference(lbttcp_module,
        "request_port_high",
        "Request port range high (default " MAKESTRING(LBTTCP_DEFAULT_REQUEST_PORT_HIGH)")",
        "Set the high end of the LBT-TCP request TCP port range (context request_tcp_port_high)",
        10,
        &global_lbttcp_request_port_high);

    prefs_register_uint_preference(lbttcp_module,
        "store_port_low",
        "UME Store port range low (default " MAKESTRING(LBTTCP_DEFAULT_STORE_PORT_LOW)")",
        "Set the low end of the LBT-TCP UME Store TCP port range",
        10,
        &global_lbttcp_store_port_low);

    prefs_register_uint_preference(lbttcp_module,
        "store_port_high",
        "UME Store port range high (default " MAKESTRING(LBTTCP_DEFAULT_STORE_PORT_HIGH)")",
        "Set the high end of the LBT-TCP UME Store TCP port range",
        10,
        &global_lbttcp_store_port_high);

    prefs_register_bool_preference(lbttcp_module,
        "use_lbttcp_domain",
        "Use LBT-TCP tag table",
        "Use table of LBT-TCP tags to decode the packet instead of above values",
        &global_lbttcp_use_tag);
    tag_uat = uat_new("LBT-TCP tag definitions",
        sizeof(lbttcp_tag_entry_t),
        "lbttcp_domains",
        true,
        (void * *)&lbttcp_tag_entry,
        &lbttcp_tag_count,
        UAT_AFFECTS_DISSECTION,
        NULL,
        lbttcp_tag_copy_cb,
        lbttcp_tag_update_cb,
        lbttcp_tag_free_cb,
        NULL,
        NULL,
        lbttcp_tag_array);
    prefs_register_uat_preference(lbttcp_module,
        "tnw_lbttcp_tags",
        "LBT-TCP Tags",
        "A table to define LBT-TCP tags",
        tag_uat);
}

/* The registration hand-off routine */
void proto_reg_handoff_lbttcp(void)
{
    static bool already_registered = false;

    if (!already_registered)
    {
        dissector_add_for_decode_as_with_preference("tcp.port", lbttcp_dissector_handle);
        heur_dissector_add("tcp", test_lbttcp_packet, "LBT over TCP", "lbttcp_tcp", proto_lbttcp, HEURISTIC_ENABLE);
    }

    /* Make sure the source port low is <= the source port high. If not, don't change them. */
    if (global_lbttcp_source_port_low <= global_lbttcp_source_port_high)
    {
        lbttcp_source_port_low = global_lbttcp_source_port_low;
        lbttcp_source_port_high = global_lbttcp_source_port_high;
    }

    /* Make sure the request port low is <= the request port high. If not, don't change them. */
    if (global_lbttcp_request_port_low <= global_lbttcp_request_port_high)
    {
        lbttcp_request_port_low = global_lbttcp_request_port_low;
        lbttcp_request_port_high = global_lbttcp_request_port_high;
    }

    /* Make sure the store port low is <= the store port high. If not, don't change them. */
    if (global_lbttcp_store_port_low <= global_lbttcp_store_port_high)
    {
        lbttcp_store_port_low = global_lbttcp_store_port_low;
        lbttcp_store_port_high = global_lbttcp_store_port_high;
    }

    lbttcp_use_tag = global_lbttcp_use_tag;

    already_registered = true;
}

/*
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 4
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * vi: set shiftwidth=4 tabstop=8 expandtab:
 * :indentSize=4:tabSize=8:noTabs=true:
 */
